package org.lds.message.center.admin.business.channel.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.lds.message.center.admin.business.channel.entity.Channel;
import org.lds.message.center.admin.business.channel.mapper.ChannelMapper;
import org.lds.message.center.admin.business.channel.service.ChannelService;
import org.lds.message.center.admin.common.enums.AdminErrorEnum;
import org.lds.message.center.common.constant.CommonConstant;
import org.lds.message.center.common.model.ResultDTO;
import org.lds.message.center.common.model.request.RegisterChannelAdminRequest;
import org.lds.message.center.common.util.AssertUtil;
import org.lds.message.center.common.util.RestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * 服务实现类
 *
 * @author lidongsheng
 * @since 2024-12-22
 */
@Slf4j
@Service
public class ChannelServiceImpl extends ServiceImpl<ChannelMapper, Channel> implements ChannelService {

    @Autowired
    private ChannelMapper channelMapper;

    private ScheduledExecutorService scheduled = Executors.newSingleThreadScheduledExecutor();

    private Map<Long, Integer> unique = new ConcurrentHashMap<>();

    @Override
    public ResultDTO<Long> registerChannel(RegisterChannelAdminRequest request) {
        AssertUtil.assertTrue(StringUtils.isNotEmpty(request.getType()), AdminErrorEnum.CHANNEL_TYPE_NOT_NULL);
        AssertUtil.assertTrue(StringUtils.isNotEmpty(request.getName()), AdminErrorEnum.CHANNEL_NAME_NOT_NULL);

        Long channelId = saveOrUpdate(request);
        if (!unique.containsKey(channelId)) {
            unique.put(channelId, 0);

            // 加入到健康检查
            scheduled.scheduleWithFixedDelay(new HealthTask(request.getHealthUrl(), unique, channelId, request, channelMapper),
                    100L, 30L, TimeUnit.SECONDS);
        }

        return ResultDTO.success(channelId);
    }

    private Long saveOrUpdate(RegisterChannelAdminRequest request) {
        Channel existChannel = channelMapper.selectOne(new QueryWrapper<>(Channel.builder()
                .type(request.getType())
                .name(request.getName())
                .build()));
        if (Objects.nonNull(existChannel)) {
            if (!BooleanUtils.isTrue(existChannel.getEnable())) {
                channelMapper.updateById(Channel.builder()
                                .id(existChannel.getId())
                                .enable(true)
                                .updateTime(LocalDateTime.now())
                                .build());
            }
            return existChannel.getId();
        }

        LocalDateTime now = LocalDateTime.now();
        Channel channel = Channel.builder()
                .type(request.getType())
                .name(request.getName())
                .desc(request.getDesc())
                .enable(true)
                .creator(CommonConstant.DEFAULT_USER)
                .modifier(CommonConstant.DEFAULT_USER)
                .createTime(now)
                .updateTime(now)
                .build();
        channelMapper.insert(channel);
        return channel.getId();
    }

    @RequiredArgsConstructor
    public static class HealthTask implements Runnable {
        private final String healthUrl;
        private final Map<Long, Integer> unique;
        private final Long channelId;
        private final RegisterChannelAdminRequest request;
        private final ChannelMapper channelMapper;

        @Override
        public void run() {
            ResultDTO resultDTO = RestUtil.get(healthUrl, null, ResultDTO.class);
            if (resultDTO != null && resultDTO.isSuccess()) {
                unique.put(channelId, 0);
                return;
            }

            // 调用失败
            Integer failCount = unique.getOrDefault(channelId, 0) + 1;
            if (failCount > 2) {
                // 当前通道已经下线
                channelMapper.updateById(Channel.builder().id(channelId).enable(false).build());
                log.warn("当前通道已经下线. type={} name={}", request.getType(), request.getName());

                unique.put(channelId, 0);
            } else {
                unique.put(channelId, failCount);
            }

        }
    }

}
